﻿using Aliyun.OSS;
using EssentialWebFramework.Models.Framework;
using Microsoft.AspNetCore.Http;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace EssentialWebFramework.Utils
{
    public class AliOSSUtils
    {
        public static readonly Regex UrlReg = new Regex("^https?://(.*?)\\.(.*?)/(.+)$");
        static readonly OssClient ossClient = new OssClient(GlobalConfig.Setting.OSS.EndPoint, GlobalConfig.Setting.OSS.AccessKeyId, GlobalConfig.Setting.OSS.AccessKeySecret);
        static readonly string ossUrlWithBucket = GlobalConfig.Setting.OSS.EndPoint.Replace("https://", $"https://{GlobalConfig.Setting.OSS.BucketName}.");

        /// <summary>
        /// OSS 对象
        /// </summary>
        public class AliOSSObject
        {            
            public string BucketName { get; set; }
            public string EndPoint { get; set; }
            public string Key { get; set; }
        }

        /// <summary>
        /// 根据 url 获取 OSS 对象
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static AliOSSObject GetOSSObject(string url)
        {
            Match match = UrlReg.Match(url);
            return new AliOSSObject
            {
                BucketName = match.Groups[1].Value,
                EndPoint = match.Groups[2].Value,
                Key = match.Groups[3].Value
            };            
        }

        /// <summary>
        /// 上传到 OSS
        /// </summary>
        /// <param name="key"></param>
        /// <param name="fileFullPath"></param>
        /// <returns></returns>
        public static string PutObjet(string key, string fileFullPath)
        {
            PutObjectResult result = ossClient.PutObject(GlobalConfig.Setting.OSS.BucketName, key, fileFullPath);
            if(result.HttpStatusCode == HttpStatusCode.OK ||
                result.HttpStatusCode == HttpStatusCode.NoContent)
            {
                return $"{ ossUrlWithBucket }/{ key }";
            }
            else
            {
                // TODO 记录日志
                return null;
            }
        }

        /// <summary>
        /// 根据 阿里云存储的视频 url 获取视频截图的 Uri
        /// </summary>
        /// <param name="objectName"></param>
        /// <returns></returns>
        public static Uri GetVideoScreenshotUrl(string url)
        {
            AliOSSObject ossObj = GetOSSObject(url);
            GeneratePresignedUriRequest req = new GeneratePresignedUriRequest(ossObj.BucketName, ossObj.Key, SignHttpMethod.Get);
            // 指定过期时间为10分钟。
            req.Expiration = DateTime.Now.AddMinutes(10);
            // 使用精确时间模式截取视频 30s 处的内容，输出为 JPG 格式的图片，宽高均为原始视频的宽高
            req.Process = "video/snapshot,t_30000,f_jpg,w_0,h_0";
            return ossClient.GeneratePresignedUri(req);
        }

        /// <summary>
        /// 生成 Post 文件的 策略
        /// </summary>
        /// <param name="expiredTime"></param>
        /// <param name="policyConditions"></param>
        /// <returns></returns>
        public static string GeneratePostPolicy(DateTime expiredTime, PolicyConditions policyConditions)
        {
            return ossClient.GeneratePostPolicy(expiredTime, policyConditions);  // 过期时间 1 小时
        }

        /// <summary>
        /// 计算 OSS 签名
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string ComputeOSSSignature(string key, string data)
        {
            using (var algorithm = KeyedHashAlgorithm.Create("HmacSHA1".ToUpperInvariant()))
            {
                algorithm.Key = Encoding.UTF8.GetBytes(key.ToCharArray());
                return Convert.ToBase64String(
                    algorithm.ComputeHash(Encoding.UTF8.GetBytes(data.ToCharArray())));
            }
        }

        /// <summary>
        /// OSS 上传后 回调参数
        /// </summary>
        public class CallbackParam
        {
            public static string CALLBACKBODY_ORIGIN = "bucket=${bucket}&object=${object}&filename=${object}&size=${size}&mimeType=${mimeType}";
            public string CallbackUrl { get; set; }
            public string CallbackBody { get; set; }
            public string CallbackBodyType { get; set; } = "application/x-www-form-urlencoded";
        }

        /// <summary>
        /// 验证回调签名
        /// </summary>
        /// <param name="callbackResponse"></param>
        /// <returns></returns>
        public static bool VerifyCallBackSignature(HttpRequest callbackRequest)
        {
            // 获取认证字段
            string authorizationBase64Str = null;            
            if (callbackRequest.Headers.ContainsKey("authorization"))
            {
                authorizationBase64Str = callbackRequest.Headers["authorization"].ToString();
            }
            else if (callbackRequest.Headers.ContainsKey("Authorization"))
            {
                authorizationBase64Str = callbackRequest.Headers["Authorization"].ToString();
            }

            // 没有签名，验证失败
            if (string.IsNullOrEmpty(authorizationBase64Str))
            {
                return false;
            }

            byte[] authorizationbyteArr = Convert.FromBase64String(authorizationBase64Str);

            // 没有公钥地址，验证失败
            if (!callbackRequest.Headers.ContainsKey("x-oss-pub-key-url"))
            {
                return false;
            }

            // 获取公钥
            byte[] publicKeyURLByteArr = Convert.FromBase64String(callbackRequest.Headers["x-oss-pub-key-url"].ToString());
            string publicKeyURL = Encoding.ASCII.GetString(publicKeyURLByteArr);
            
            string publicKeyBase64Str = null;

            using(HttpClient httpClient = new HttpClient())
            using(StreamReader sr = new StreamReader(httpClient.GetStreamAsync(publicKeyURL).Result, Encoding.UTF8))
            {
                publicKeyBase64Str = sr.ReadToEnd();
            }
            publicKeyBase64Str = publicKeyBase64Str.Replace("-----BEGIN PUBLIC KEY-----\n", "").Replace("-----END PUBLIC KEY-----", "").Replace("\n", ""); 
            string publicKeyContentXML = RSAPublicKeyString2XML(publicKeyBase64Str);

            string url = callbackRequest.Path;
            string query = callbackRequest.QueryString.Value;

            // 由于框架无法读取 body，手动拼接 Form 中的内容
            string body = string.Join("&", callbackRequest.Form.Keys.Select(a=> $"{ a }={ CommonUtils.UrlEncodeUpper(callbackRequest.Form[a]) }"));

            string authSourceForMD5Str = null;
            if (!string.IsNullOrEmpty(authSourceForMD5Str))
            {
                authSourceForMD5Str = $"{ HttpUtility.UrlDecode(url) }?{ query }\n{ body }";
            }
            else
            {
                authSourceForMD5Str = $"{ HttpUtility.UrlDecode(url) }\n{ body }";
            }
                
                
            byte[] authMD5ByteArr = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(authSourceForMD5Str));

            // 验证签名
            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
            RSA.FromXmlString(publicKeyContentXML);
            RSAPKCS1SignatureDeformatter RSADeformatter = new RSAPKCS1SignatureDeformatter(RSA);
            RSADeformatter.SetHashAlgorithm("MD5");
            return RSADeformatter.VerifySignature(authMD5ByteArr, authorizationbyteArr);
        }

        private static string RSAPublicKeyString2XML(string publicKey)
        {
            RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));
            return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>",
                Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()),
                Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned()));
        }

        /// <summary>
        /// 获取带签名的 OSS 地址
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string GetOSSUrlWithSign(string url)
        {
            if (url == null || !url.Contains("aliyun"))
            {
                return url;
            }

            // 过期时间格式为 1970年至目标时间 的秒数
            const long ticksOf1970 = 621355968000000000;
            long expiredTimestamp = (DateTime.UtcNow.Ticks - ticksOf1970) / 10000000L + 60 * 60;    // 过期时间为 1 小时

            AliOSSObject ossObj = GetOSSObject(url);

            string data = $"GET\n\n\n{ expiredTimestamp }\n/{ ossObj.BucketName }/{ ossObj.Key }";    // 要加密的数据
            string sign = HttpUtility.UrlEncode(ComputeOSSSignature(GlobalConfig.Setting.OSS.AccessKeySecret, data));

            return $"{ url }?Expires={ expiredTimestamp }&OSSAccessKeyId={GlobalConfig.Setting.OSS.AccessKeyId }&Signature={ sign }";
        }

        private static Regex SrcUrlReg = new Regex("src=\"(.*?)\"");

        /// <summary>
        /// 为内容中的对象存储 url 设置签名参数
        /// </summary>
        public static string GetOSSUrlContentWithSign(string content)
        {
            if (SrcUrlReg.IsMatch(content))
            {
                MatchCollection matchCollection = SrcUrlReg.Matches(content);
                foreach(Match match in matchCollection)
                {
                    content = content.Replace(match.Groups[1].Value, GetOSSUrlWithSign(match.Groups[1].Value));
                }
            }
            return content;
        }

        public static void ListObject(string bucketName, string prefix)
        {
            ObjectListing result = null;
            string nextMarker = string.Empty;
            do
            {
                // 每页列举的文件个数通过mMxKeys指定，超出指定数量的文件将分页显示。
                var listObjectsRequest = new ListObjectsRequest(bucketName)
                {
                    Marker = nextMarker,
                    MaxKeys = 100
                };

                if (!string.IsNullOrEmpty(prefix))
                {
                    listObjectsRequest.Prefix = prefix;
                }

                result = ossClient.ListObjects(listObjectsRequest);
                foreach (var summary in result.ObjectSummaries)
                {
                    Console.WriteLine("Name:{0}", summary.Key);
                }
                nextMarker = result.NextMarker;
            } while (result.IsTruncated);
        }
    }

    /// <summary>
    /// AliOSS 上传所需的 Policy
    /// </summary>
    public class AliOSSPostPolicy
    {
        /// <summary>
        /// URL
        /// </summary>
        public string URL { get; set; }
        /// <summary>
        /// 密钥 id
        /// </summary>
        public string OSSAccessKeyId { get; set; }
        /// <summary>
        /// Policy
        /// </summary>
        public string Policy { get; set; }
        /// <summary>
        /// 签名
        /// </summary>
        public string Signature { get; set; }
        /// <summary>
        /// 回调
        /// </summary>
        public string Callback { get; set; }
    }
}
